/*
 * The game of "SET"
 *
 * The game of set consists of a deck of 81 cards, which represents
 * one each of all possible combinations of these attributes:
 *
 *		Shape:		Diamond		Oval		Squiggle
 *		Colour: 	Red			Blue		Green
 *		Texture:	Clear		Shaded		Solid
 *		Number:		One			Two			Three
 *
 * At any given time, the game displays a selection of cards on the
 * screen, and the challange is to be the first to recognize a "set"
 * of cards.
 *
 * A "set" of cards is defined as any three cards, in which for EACH
 * of the above attributes, they are either "All the Same", or "All
 * Different". For example, cards in a set will have either all the
 * same shape, or all different shapes. If two cards have the same
 * shape, and the third is different, then they are NOT a set. This
 * criteria must be applied to EACH of the four attributes - if any
 * single attribute is the same on two cards and different on the
 * third, then the cards are NOT a set.
 *
 * Up to nine players may participate. When you think you have identified
 * a set, press the keyboard digit corresponding to your number (1-9).
 * The screen will clear (so you can't hit your key and then look for a
 * set), and you will be asked to enter the three letters which identify
 * the cards in your set.
 *
 * If you think there are no more sets, press 'C' to deal another card.
 * Press 'D' to reshuffle and redeal the deck.
 * Press 'S' to show a list of the sets currently on the screen.
 * Press ' ' to redraw the display (after Show)
 * Press ESC to end the game.
 *
 * Copyright 2005-2007 Dave Dunfield.
 */
#include <stdio.h>
#include <hrg.h>
#include <keys.h>

#define	TICK		peekw(0x40,0x6C)
#define	MLINE		465			// Message line

#define	CWIDTH		100			// Card width
#define	CHEIGHT		150			// Card height
#define	SWIDTH		80			// Shape width
#define	CARDS		(3*3*3*3)	// Number of cards

// 3 shapes * 3 textures * 3 colors * 3
char diamond[SWIDTH][2] = {
	  0, 0  ,	  0, 0  ,	  0, 0  ,	  1, -1 ,	  1, -1 ,
	  1, -1 ,	  2, -2 ,	  2, -2 ,	  3, -3 ,	  3, -3 ,
	  3, -3 ,	  4, -4 ,	  4, -4 ,	  4, -4 ,	  5, -5 ,
	  5, -5 ,	  6, -6 ,	  6, -6 ,	  6, -6 ,	  7, -7 ,
	  7, -7 ,	  7, -7 ,	  8, -8 ,	  8, -8 ,	  9, -9 ,
	  9, -9 ,	  9, -9 ,	 10, -10,	 10, -10,	 10, -10,
	 11, -11,	 11, -11,	 12, -12,	 12, -12,	 12, -12,
	 13, -13,	 13, -13,	 13, -13,	 14, -14,	 14, -14,
	 15, -15,	 14, -14,	 14, -14,	 13, -13,	 13, -13,
	 13, -13,	 12, -12,	 12, -12,	 12, -12,	 11, -11,
	 11, -11,	 10, -10,	 10, -10,	 10, -10,	  9, -9 ,
	  9, -9 ,	  9, -9 ,	  8, -8 ,	  8, -8 ,	  7, -7 ,
	  7, -7 ,	  7, -7 ,	  6, -6 ,	  6, -6 ,	  6, -6 ,
	  5, -5 ,	  5, -5 ,	  4, -4 ,	  4, -4 ,	  4, -4 ,
	  3, -3 ,	  3, -3 ,	  3, -3 ,	  2, -2 ,	  2, -2 ,
	  1, -1 ,	  1, -1 ,	  1, -1 ,	  0, 0  ,	  0, 0  };

char oval[SWIDTH][2] = {
	  0, 0  ,	  5,  -5,	  8, -8 ,	 10, -10,	 11, -11,
	 12, -12,	 12, -12,	 13, -13,	 13, -13,	 13, -13,
	 14, -14,	 14, -14,	 14, -14,	 14, -14,	 15, -15,
	 15, -15,	 15, -15,	 15, -15,	 15, -15,	 15, -15,
	 15, -15,	 15, -15,	 15, -15,	 15, -15,	 15, -15,
	 15, -15,	 15, -15,	 15, -15,	 15, -15,	 15, -15,
	 15, -15,	 15, -15,	 15, -15,	 15, -15,	 15, -15,
	 15, -15,	 15, -15,	 15, -15,	 15, -15,	 15, -15,
	 15, -15,	 15, -15,	 15, -15,	 15, -15,	 15, -15,
	 15, -15,	 15, -15,	 15, -15,	 15, -15,	 15, -15,
	 15, -15,	 15, -15,	 15, -15,	 15, -15,	 15, -15,
	 15, -15,	 15, -15,	 15, -15,	 15, -15,	 15, -15,
	 15, -15,	 15, -15,	 15, -15,	 15, -15,	 15, -15,
	 15, -15,	 14, -14,	 14, -14,	 14, -14,	 14, -14,
	 13, -13,	 13, -13,	 13, -13,	 12, -12,	 12, -12,
	 11, -11,	 10, -10,	  8, -8 ,	  5, -5 ,	  0, 0  } ;

char squiggle[SWIDTH][2] = {
	 10, 10 ,	 13, 10,	 15, 9 ,	 15, 9  ,	 15, 8  ,
	 15, 8  ,	 15, 7  ,	 15, 5  ,	 14, 3  ,	 14, 0  ,
	 14, -2 ,	 13, -4 ,	 13, -5 ,	 12, -6 ,	 11, -7 ,
	 10, -8 ,	 10, -8 ,	  9, -9 ,	  9, -9 ,	  9, -10,
	  9, -10,	  8, -11,	  8, -11,	  8, -11,	  8, -12,
	  8, -12,	  7, -12,	  7, -12,	  7, -13,	  8, -13,
	  8, -13,	  8, -13,	  8, -13,	  9, -13,	  9, -13,
	  9, -12,	 10, -12,	 10, -12,	 11, -11,	 11, -11,
	 11, -11,	 11, -11,	 12, -10,	 12, -10,	 12, -9 ,
	 13, -9 ,	 13, -9 ,	 13, -8 ,	 13, -8 ,	 13, -8 ,
	 13, -8 ,	 13, -7 ,	 12, -7 ,	 12, -7 ,	 12, -8 ,
	 12, -8 ,	 11, -8 ,	 11, -8 ,	 11, -8 ,	 10, -9 ,
	  9, -9,	  9, -9 ,	  9, -9 ,	  8, -10,	  8, -10,
	  7, -11,	  6, -12,	  5, -13,	  4, -13,	  2, -14,
	  0, -14,	 -3, -14,	 -5, -15,	 -7, -15,	 -8, -15,
	 -8, -15,	 -9,-15 ,	 -9 ,-15 ,	-10, -13,	-10, -10 };

unsigned
	hint = 18*60,		// Time before first hint given
	nexthint = 18*10,	// Time to subsequent hints
	hintlen = 2,		// Duration of hint
	players = 9,		// Number of players
	score[9],			// Player scores
	xy[18][2];			// X/Y coordinates of displayed cards

unsigned char
	dpos,			// Position in deck
	dend,			// Ending position in deck
	dsize,			// Total size of deck
	max = 9,		// Maximum # cards
	dealt,			// # of cards dealt
	deck[CARDS],	// Cards in deck
	sets[18][3],	// Card sets
	stop,			// # of card sets
	select[18];		// Selection flags

extern unsigned
	RAND_SEED;

register message(unsigned args)
{
	char buffer[81];

	_format_(nargs() * 2 + &args, buffer);
	hrg_fbox(0, MLINE, 640, 8, BLACK);
	hrg_puts(1, MLINE, WHITE, buffer);
}

void draw_shape(unsigned char s[SWIDTH][2], unsigned t, unsigned c, unsigned x, unsigned y)
{
	unsigned i;
	char l;
	int lx0, lx1, ly0, ly1;
	lx0 = lx1 = x;
	ly0 = (int)s[0][0]+y;
	ly1 = (int)s[0][1]+y;
	switch(t) {
	case 0 :	// Clear
		for(i=1; i < SWIDTH; ++i) {
			hrg_line(lx0, ly0, lx0=x+i, ly0=(int)s[i][0]+y, c);
			hrg_line(lx1, ly1, lx1=x+i, ly1=(int)s[i][1]+y, c); }
		return;
	case 1 :	// Shade
		for(i=0; i < SWIDTH; ++i) {
			if(i & 3) {
				hrg_line(lx0, ly0, lx0=x+i, ly0=(int)s[i][0]+y, c);
				hrg_line(lx1, ly1, lx1=x+i, ly1=(int)s[i][1]+y, c);
				continue; }
			if(l = s[i][0] - s[i][1]) {
				ly0 = (int)s[i][0]+y;
				hrg_vline(lx0 = lx1 = x+i, ly1 = (int)s[i][1]+y, l, c); } }
		return;
	case 2 :	// Fill
		for(i=0; i < SWIDTH; ++i) {
			if(l = s[i][0] - s[i][1])
				hrg_vline(x+i, (int)s[i][1]+y, l, c); } }
}

void draw_card(unsigned x, unsigned y, unsigned card, unsigned b)
{
	unsigned c, unsigned s, unsigned t, unsigned n;
	static unsigned char colors[] = { RED, GREEN, BLUE };
	static char *shapes[] = { &oval, &diamond, &squiggle };

	hrg_fbox(x, y, CWIDTH, CHEIGHT, b);

	n = card % 3; card /= 3;
	t = card % 3; card /= 3;
	s = card % 3; card /= 3;
	c = colors[card % 3];

	switch(n) {
	case 0 :		// Single shape
		draw_shape(shapes[s], t, c, x+((CWIDTH-SWIDTH)/2), y + 75);
		return;
	case 1 :		// Two shapes
		draw_shape(shapes[s], t, c, x+((CWIDTH-SWIDTH)/2), y + 51);
		draw_shape(shapes[s], t, c, x+((CWIDTH-SWIDTH)/2), y + 99);
		return;
	case 2 :		// Three shapes
		draw_shape(shapes[s], t, c, x+((CWIDTH-SWIDTH)/2), y + 31);
		draw_shape(shapes[s], t, c, x+((CWIDTH-SWIDTH)/2), y + 75);
		draw_shape(shapes[s], t, c, x+((CWIDTH-SWIDTH)/2), y + 119); }
}

void draw_deck(unsigned max)
{
	unsigned mx, x, y, x1, y1, i, j;
	i = sizeof(deck) - dpos;
	if(i < max)
		max = i;
	mx = 6;
	if(max <= 15)	mx = 5;
	if(max <= 12)	mx = 4;
	if(max <= 9)	mx = 3;
	if(max <= 4)	mx = 2;
	x = y = j = 0;
	dend = i = dpos;
	hrg_fbox(0, 0, 639, 479, BLACK);
	while(i < sizeof(deck)) {
		dend = i;
		if(deck[i] != 255) {
			draw_card(x1 = (x*105), y1 = y*155, deck[i], WHITE);
			hrg_putc(x1+1, y1+1, WHITE<<8, j + 'A'); }
		xy[j][0] = x1;
		xy[j][1] = y1;
		if(++j >= max)
			return;
		if(++x >= mx) {
			x = 0;
			if(++y >= 3)
				return; }
		++i; }
}

int getcard(unsigned p)
{
	unsigned c, d;
	for(;;) {
		switch(c = toupper(kbget())) {
		case 0x1B: return 255; }
		c -= 'A';
		d = dpos + c;
		if(d <= dend) {
			if((deck[d] != 255) && !select[c]) {
				select[c] = 255;
				draw_card(xy[c][0], xy[c][1], deck[d], WHITE);
				hrg_putc((p*10)+155, MLINE, WHITE, c+'A');
				return d; } }
		beep(1000, 500); }
}

int test_attribute(unsigned a, unsigned b, unsigned c)
{
	if((a == b) && (a == c))				// Same
		return 255;
	if((a != b) && (a != c) && (b != c))	// Different
		return 255;
	return 0;
}

int test_set(unsigned card1, unsigned card2, unsigned card3)
{
	unsigned n1, n2, n3, t1, t2, t3, s1, s2, s3, c1, c2, c3;

	card1 = deck[card1];
	card2 = deck[card2];
	card3 = deck[card3];
	if((card1 == 255) || (card2 == 255) || (card3 == 255))
		return 0;

	n1 = card1 % 3; card1 /= 3;
	t1 = card1 % 3; card1 /= 3;
	s1 = card1 % 3; card1 /= 3;
	c1 = card1 % 3;

	n2 = card2 % 3; card2 /= 3;
	t2 = card2 % 3; card2 /= 3;
	s2 = card2 % 3; card2 /= 3;
	c2 = card2 % 3;

	n3 = card3 % 3; card3 /= 3;
	t3 = card3 % 3; card3 /= 3;
	s3 = card3 % 3; card3 /= 3;
	c3 = card3 % 3;

	return	test_attribute(n1, n2, n3)
		&&	test_attribute(t1, t2, t3)
		&&	test_attribute(s1, s2, s3)
		&&	test_attribute(c1, c2, c3);
}

int deal_card()
{
	unsigned i;
	if(dend >= dsize) {
		beep(1000, 500);
		return 0; }
	for(i=dpos; i <= dend; ++i) {
		if(deck[i] == 255) {
			deck[i] = deck[--dsize];
			return 255; } }
	return 0;
}

xget()
{
	unsigned c, d, t, h;
	unsigned char f;
	t = TICK + hint;
	f = 0;
	for(;;) {
		do {
			if(c = kbtst())
				return toupper(c); }
		while(TICK < t);
		if(stop) {					// Sets available
			if(!f) {				// Turning hint on
				h = random(stop);
				for(c=0; c < 3; ++c) {
					d = sets[h][c];
					hrg_putc(xy[d][0]+1, xy[d][1]+1, WHITE<<8, ' '); }
				f = 255;
				t += hintlen;
				continue; }
			for(c=0; c < 3; ++c) {
				d = sets[h][c];
				hrg_putc(xy[d][0]+1, xy[d][1]+1, WHITE<<8, d+'A'); }
			f = 0;
			t += nexthint;
			continue; }
		beep(1000, 55);
		f = 0;
		t += hint; }
}
	
main()
{
	unsigned i, j, k;
	unsigned char c1, c2, c3;

	if(hrg_open())
		abort("VGA required");

deal:
	RAND_SEED = TICK;
	for(i=0; i < sizeof(deck); ++i)
		deck[i] = i;
	for(i=0; i < sizeof(deck); ++i) {
		j = random(sizeof(deck));
		k = deck[i];
		deck[i] = deck[j];
		deck[j] = k; }
	dpos = 0;
	dsize = sizeof(deck);
	dealt = max;
x2:
	if((dpos + dealt) >= dsize)
		dealt = dsize - dpos;
	draw_deck(dealt);
	// Scan for available sets
	stop = 0;
	for(i=dpos; i <= dend; ++i) {
		for(j=i+1; j <= dend; ++j) {
			for(k=j+1; k <= dend; ++k) {
				if(test_set(i, j, k)) {
					sets[stop][0] = i;
					sets[stop][1] = j;
					sets[stop++][2] = k; } } } }
x3:
	message("C)ard  D)eal  S)how");
	for(i=0; i < players; ++i)
		hrg_printf((i*50)+200, MLINE, WHITE, "%u", score[i]);
x4:
	memset(select, 0, sizeof(select));
	for(;;) switch(i=xget()) {
	case ' ' : goto x2;
	case 0x1B: hrg_close(); return;
	case 'C' : if(!deal_card()) ++dealt; goto x2;
	case 'D' : goto deal;
	case 'S' :
		hrg_fbox(0, MLINE, 640, 8, BLACK);
		j = 1;
		for(i=0; i < stop; ++i) {
			if(i) {
				hrg_putc(j, MLINE, WHITE, ',');
				j += 8; }
			hrg_printf(j, MLINE, WHITE, "%c%c%c", sets[i][0]+'A', sets[i][1]+'A', sets[i][2]+'A');
			j += (8*3); }
		if(j == 1) {
			beep(1000, 55);
			goto x3; }
		goto x4;
	default:
		i -= '1';
		if(i < players) {
			hrg_fbox(0, 0, 640, 480, BLACK);
			message("Player%u enter cards:", i+1);
			if((c1 = getcard(1)) == 255) goto x2;
			if((c2 = getcard(2)) == 255) goto x2;
			if((c3 = getcard(3)) == 255) goto x2;
			if(test_set(c1, c2, c3)) {
				++score[i];
				deck[c1] = 255;
				deck[c2] = 255;
				deck[c3] = 255;
				for(i=0; i < 5; ++i) {
					beep((i*100)+1000, 55);
					delay(100); }
				goto x2; }
			beep(1000, 500);
			goto x2; } }
}
